package com.haidechizi.util.lock.impl;

import com.haidechizi.util.lock.AbstractDistributeLock;
import com.haidechizi.util.lock.exception.DistributeLockException;
import com.haidechizi.util.lock.properties.CuratorProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author: wangdejun
 * @create: 2019-11-12 17:39
 **/
@Slf4j
public class CuratorDistributeLock extends AbstractDistributeLock {

    private static final Map<String, InterProcessLock> lockMap = new ConcurrentHashMap<>();


    @Autowired
    private CuratorFramework framework;

    @Autowired
    private CuratorProperties curatorProperties;

    /**
     * 类型
     *
     * @return
     */
    @Override
    public String type() {
        return "curator";
    }

    /**
     * 加锁
     *
     * @param lockKey
     * @return
     */
    @Override
    public void lock(String lockKey) {
        lockKey = getLockKey(lockKey);
        InterProcessLock lock = getLock(lockKey);
        try {
            lock.acquire();
        } catch (Exception e) {
            log.error("curator获取锁失败", e);
            throw new DistributeLockException("curator获取锁失败", e);
        }


    }

    /**
     * 带超时的锁
     *
     * @param lockKey
     * @param timeout 超时时间   单位：秒
     */
    @Override
    public void lock(String lockKey, long timeout) {
        lockKey = getLockKey(lockKey);
        InterProcessLock lock = getLock(lockKey);
        try {
            lock.acquire(timeout, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("curator获取锁失败", e);
            throw new DistributeLockException("curator获取锁失败", e);
        }

    }

    /**
     * 带超时的锁
     *
     * @param lockKey
     * @param unit    时间单位
     * @param timeout 超时时间
     */
    @Override
    public void lock(String lockKey, TimeUnit unit, long timeout) {
        lockKey = getLockKey(lockKey);
        InterProcessLock lock = getLock(lockKey);
        try {
            lock.acquire(timeout, unit);
        } catch (Exception e) {
            log.error("curator获取锁失败", e);
            throw new DistributeLockException("curator获取锁失败", e);
        }

    }

    /**
     * 尝试获取锁
     *
     * @param lockKey
     * @param unit      时间单位
     * @param waitTime  最多等待时间
     * @param leaseTime 上锁后自动释放锁时间
     * @return
     */
    @Override
    public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
        lockKey = getLockKey(lockKey);
        InterProcessLock lock = getLock(lockKey);
        try {
            return lock.acquire(waitTime, unit);
        } catch (Exception e) {
            log.error("curator获取锁失败", e);
            throw new DistributeLockException("curator获取锁失败", e);
        }
    }

    /**
     * 释放锁
     *
     * @param lockKey
     */
    @Override
    public void unlock(String lockKey) {
        lockKey = getLockKey(lockKey);
        InterProcessLock lock = getLock(lockKey);
        try {
            lock.release();
        } catch (Exception e) {
            log.error("curator释放锁失败", e);
            throw new DistributeLockException("curator释放锁失败", e);
        }

    }

    /**
     * 格式化lockKey
     *
     * @param lockKey
     * @return
     */
    private String getLockKey(String lockKey) {
        lockKey = "/" + lockKey;
        lockKey = lockKey.replaceAll("/{2,}", "/");
        return lockKey;
    }

    private InterProcessLock getLock(final String lockKey) {
        if (lockMap.containsKey(lockKey)) {
            return lockMap.get(lockKey);
        }
        synchronized (lockKey) {
            if (lockMap.containsKey(lockKey)) {
                lockMap.get(lockKey);
            }
            try {
                Class<?> clazz = Class.forName(curatorProperties.getLockClass());
                Constructor constructor = clazz.getConstructor(CuratorFramework.class, String.class);
                InterProcessLock lock = (InterProcessLock) constructor.newInstance(framework, lockKey);
                lockMap.put(lockKey, lock);
                return lock;
            } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
                throw new DistributeLockException(e);
            }
        }
    }
}
